﻿using System;
using System.Collections.Generic;
using Notoric.Commanding.CommandBase;

namespace Notoric.ViewModel
{
    /// <summary>
    /// An undoable command for changing the time signature.
    /// </summary>
    public class ChangeTimeSignatureCommand : UndoableCommandBase
    {
        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="ChangeTimeSignatureCommand"/> class.
        /// </summary>
        /// <param name="undoStack">The undo command stack</param>
        /// <param name="redoStack">The redo command stack</param>
        public ChangeTimeSignatureCommand(CommandStack undoStack, CommandStack redoStack)
            : base(undoStack, redoStack)
        {
            AddParameter<ushort>("Beats", ParameterDirection.In);
            AddParameter<ushort>("BeatUnit", ParameterDirection.In);
            AddParameter<Elements.Composition>("Composition", ParameterDirection.In);
            AddParameter<ushort>("FromMeasure", ParameterDirection.In);
            AddParameter<ushort>("ToMeasure", ParameterDirection.In);
            AddParameter<Elements.TimeSignatureType>("Type", ParameterDirection.In);

            Name = "Change Time Signature";
            Beats = 4;
            BeatUnit = 4;

            modifiedMeasures = new List<Model.Elements.TimeSignatureElement>();
        }

        #endregion

        #region Command Logic

        /// <inheritdoc/>
        protected override void CommandOperation(object parameter)
        {
            var modelComposition = (Model.Elements.CompositionElement)Composition.AdaptedElement;
            var modelPart = (Model.Packaging.CompositionPart)modelComposition.PackagePart;
            var modelType = (Model.Elements.TimeSignatureType)Enum.Parse(typeof(Model.Elements.TimeSignatureType), Type.ToString());
            var fromMeasure = modelComposition[FromMeasure];
            var toMeasure = modelComposition[ToMeasure];

            modelPart.RaiseGlobalChangePending(fromMeasure, toMeasure);

            for (ushort i = FromMeasure; i <= ToMeasure; ++i)
            {
                var measure = modelComposition[i];
                modifiedMeasures.Add(measure.Meter);

                if (Type == Elements.TimeSignatureType.Standard)
                    measure.Meter = new Model.Elements.TimeSignatureElement(modelPart, measure, Beats, BeatUnit);
                else
                    measure.Meter = new Model.Elements.TimeSignatureElement(modelPart, measure, modelType);
            }

            var references = modelComposition.GetReferencingPages(fromMeasure, toMeasure);
            foreach (var reference in references)
            {
                foreach (var system in ((Model.Elements.CWNotation.MusicSectionInterior)reference.Section.Interior).Systems)
                    Model.Elements.CWNotation.Layout.Layouter.LayoutHorizontally(system);
            }

            Model.Elements.CWNotation.Layout.Layouter.LayoutHorizontallyNA(((Model.Elements.CWNotation.MusicSectionInterior)modelPart.LinearView.MusicSection.Interior).Systems[0]);

            Composition.FindOwner<Elements.Project>().IsModified = true;
        }

        /// <inheritdoc/>
        protected override void RedoOperation()
        {
            var modelComposition = (Model.Elements.CompositionElement)Composition.AdaptedElement;
            var modelPart = (Model.Packaging.CompositionPart)modelComposition.PackagePart;
            var modelType = (Model.Elements.TimeSignatureType)Enum.Parse(typeof(Model.Elements.TimeSignatureType), Type.ToString());
            var fromMeasure = modelComposition[FromMeasure];
            var toMeasure = modelComposition[ToMeasure];

            modelPart.RaiseGlobalChangePending(fromMeasure, toMeasure);

            for (ushort i = FromMeasure, j = 0; i <= ToMeasure; ++i, ++j)
            {
                var measure = modelComposition[i];

                if (Type == Elements.TimeSignatureType.Standard)
                    measure.Meter = new Model.Elements.TimeSignatureElement(modelPart, measure, Beats, BeatUnit);
                else
                    measure.Meter = new Model.Elements.TimeSignatureElement(modelPart, measure, modelType);
            }

            var references = modelComposition.GetReferencingPages(fromMeasure, toMeasure);
            foreach (var reference in references)
            {
                foreach (var system in ((Model.Elements.CWNotation.MusicSectionInterior)reference.Section.Interior).Systems)
                    Model.Elements.CWNotation.Layout.Layouter.LayoutHorizontally(system);
            }

            Model.Elements.CWNotation.Layout.Layouter.LayoutHorizontallyNA(((Model.Elements.CWNotation.MusicSectionInterior)modelPart.LinearView.MusicSection.Interior).Systems[0]);

            Composition.FindOwner<Elements.Project>().IsModified = true;
        }

        /// <inheritdoc/>
        protected override void UndoOperation()
        {
            var modelComposition = (Model.Elements.CompositionElement)Composition.AdaptedElement;
            var modelPart = (Model.Packaging.CompositionPart)modelComposition.PackagePart;
            var fromMeasure = modelComposition[FromMeasure];
            var toMeasure = modelComposition[ToMeasure];

            modelPart.RaiseGlobalChangePending(fromMeasure, toMeasure);

            for (ushort i = FromMeasure, j = 0; i <= ToMeasure; ++i, ++j)
            {
                var measure = modelComposition[i];
                measure.Meter = modifiedMeasures[j];
            }

            var references = modelComposition.GetReferencingPages(fromMeasure, toMeasure);
            foreach (var reference in references)
            {
                foreach (var system in ((Model.Elements.CWNotation.MusicSectionInterior)reference.Section.Interior).Systems)
                    Model.Elements.CWNotation.Layout.Layouter.LayoutHorizontally(system);
            }

            Model.Elements.CWNotation.Layout.Layouter.LayoutHorizontallyNA(((Model.Elements.CWNotation.MusicSectionInterior)modelPart.LinearView.MusicSection.Interior).Systems[0]);

            Composition.FindOwner<Elements.Project>().IsModified = true;
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets or sets the number of beats in the measure.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="UInt16"/>
        /// </para>
        /// <para>
        /// The number of beats in the measure. This value is ignored if the <see cref="Type"/> is not set to <see cref="Elements.TimeSignatureType.Standard"/>.<br/>
        /// This property wraps the "Beats" command parameter.
        /// </para>
        /// </value>
        public ushort Beats
        {
            get { return GetParameterValue<ushort>("Beats"); }
            set { SetParameterValue<ushort>("Beats", value); }
        }

        /// <summary>
        /// Gets or sets the beat unit.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="UInt16"/>
        /// </para>
        /// <para>
        /// The beat unit. This value is ignored if the <see cref="Type"/> is not set to <see cref="Elements.TimeSignatureType.Standard"/>.<br/>
        /// This property wraps the "BeatUnit" command parameter.
        /// </para>
        /// </value>
        public ushort BeatUnit
        {
            get { return GetParameterValue<ushort>("BeatUnit"); }
            set { SetParameterValue<ushort>("BeatUnit", value); }
        }

        /// <summary>
        /// Gets or sets the modified composition.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Elements.Composition"/>
        /// </para>
        /// <para>
        /// The modified composition.<br/>
        /// This property wraps the "Composition" command parameter.
        /// </para>
        /// </value>
        public Elements.Composition Composition
        {
            get { return GetParameterValue<Elements.Composition>("Composition"); }
            set { SetParameterValue<Elements.Composition>("Composition", value); }
        }

        /// <summary>
        /// Gets or sets the first modified measure.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="UInt16"/>
        /// </para>
        /// <para>
        /// The first modified measure.<br/>
        /// This property wraps the "FromMeasure" command parameter.
        /// </para>
        /// </value>
        public ushort FromMeasure
        {
            get { return GetParameterValue<ushort>("FromMeasure"); }
            set { SetParameterValue<ushort>("FromMeasure", value); }
        }

        /// <summary>
        /// Gets or sets the last modified measure.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="UInt16"/>
        /// </para>
        /// <para>
        /// The last modified measure.<br/>
        /// This property wraps the "ToMeasure" command parameter.
        /// </para>
        /// </value>
        public ushort ToMeasure
        {
            get { return GetParameterValue<ushort>("ToMeasure"); }
            set { SetParameterValue<ushort>("ToMeasure", value); }
        }

        /// <summary>
        /// Gets or sets the type of the time signature.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Elements.TimeSignatureType"/>
        /// </para>
        /// <para>
        /// The type of the time signature.<br/>
        /// This property wraps the "Type" command parameter.
        /// </para>
        /// </value>
        public Elements.TimeSignatureType Type
        {
            get { return GetParameterValue<Elements.TimeSignatureType>("Type"); }
            set { SetParameterValue<Elements.TimeSignatureType>("Type", value); }
        }

        #endregion

        #region Private Fields

        /// <summary>
        /// Holds the modified measures.
        /// </summary>
        private List<Model.Elements.TimeSignatureElement> modifiedMeasures;

        #endregion
    }
}
